package com.swak.persistence;

import java.time.LocalDateTime;
import java.util.List;

import org.springframework.util.Assert;

import com.swak.App;
import com.swak.define.TableDefine;
import com.swak.define.TableDefines;
import com.swak.entity.IdEntity;
import com.swak.entity.Page;
import com.swak.entity.Parameters;
import com.swak.incrementer.IdGen;
import com.swak.utils.Lists;
import com.swak.utils.StringUtils;

import lombok.EqualsAndHashCode;
import lombok.ToString;

/**
 * 使用 AR 模式： 必须定义 _mapper， 是通过 _mapper 来代理运行的
 * 
 * @author lifeng
 * @date 2021年1月29日 下午4:01:22
 */
@SuppressWarnings({ "unchecked" })
public class Model<T, PK> extends IdEntity<T, PK> {

	@EqualsAndHashCode.Exclude
	@ToString.Exclude
	private transient BaseMapper<T, PK> _mapper;

	@EqualsAndHashCode.Exclude
	@ToString.Exclude
	private transient TableDefine<T> _table;

	@EqualsAndHashCode.Exclude
	@ToString.Exclude
	private transient Selective _query;

	@EqualsAndHashCode.Exclude
	@ToString.Exclude
	private transient Parameters _param;

	@EqualsAndHashCode.Exclude
	@ToString.Exclude
	private transient List<T> _batch;

	@EqualsAndHashCode.Exclude
	@ToString.Exclude
	private transient int _limit;

	/**
	 * 最近一次操作影响的行数
	 */
	@EqualsAndHashCode.Exclude
	@ToString.Exclude
	private transient int _affectedRows;

	protected Selective newQuery() {
		if (_query == null) {
			_query = Selective.of();
		}
		return _query;
	}

	protected Parameters newParam() {
		if (_param == null) {
			_param = new Parameters();
		}
		return _param;
	}

	protected List<T> newBatch() {
		if (_batch == null) {
			_batch = Lists.newArrayList();
		}
		return _batch;
	}

	public T addBatch() {
		newBatch().add(as());
		return as();
	}

	public T addBatch(T e) {
		newBatch().add(e);
		return as();
	}

	public T addBatch(List<T> es) {
		newBatch().addAll(es);
		return as();
	}

	public T limit(int _limit) {
		this._limit = _limit;
		return as();
	}

	public T pageIndex(int pageIndex) {
		newParam().setPageIndex(pageIndex);
		return as();
	}

	public T pageSize(int pageSize) {
		newParam().setPageSize(pageSize);
		return as();
	}

	public T andIsNull(String property) {
		if (StringUtils.isNotBlank(property)) {
			newQuery().getCriteria().andIsNull(property);
		}
		return as();
	}

	public T andIsNotNull(String property) {
		if (StringUtils.isNotBlank(property)) {
			newQuery().getCriteria().andIsNotNull(property);
		}
		return as();
	}

	public T andConditionSql(String conditionSql) {
		if (StringUtils.isNotBlank(conditionSql)) {
			newQuery().getCriteria().andConditionSql(conditionSql);
		}
		return as();
	}

	public T andEqualTo(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andEqualTo(property, value);
		}
		return as();
	}

	public T andNotEqualTo(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andNotEqualTo(property, value);
		}
		return as();
	}

	public T andGreaterThan(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andGreaterThan(property, value);
		}
		return as();
	}

	public T andGreaterThanOrEqualTo(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andGreaterThanOrEqualTo(property, value);
		}
		return as();
	}

	public T andLessThan(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andLessThan(property, value);
		}
		return as();
	}

	public T andLessThanOrEqualTo(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andLessThanOrEqualTo(property, value);
		}
		return as();
	}

	public T andIn(String property, List<?> values) {
		if (StringUtils.isNotBlank(property) && values != null) {
			newQuery().getCriteria().andIn(property, values);
		}
		return as();
	}

	public T andNotIn(String property, List<?> values) {
		if (StringUtils.isNotBlank(property) && values != null) {
			newQuery().getCriteria().andNotIn(property, values);
		}
		return as();
	}

	public T andBetween(String property, Object value1, Object value2) {
		if (StringUtils.isNotBlank(property) && value1 != null && value2 != null) {
			newQuery().getCriteria().andBetween(property, value1, value2);
		}
		return as();
	}

	public T andNotBetween(String property, Object value1, Object value2) {
		if (StringUtils.isNotBlank(property) && value1 != null && value2 != null) {
			newQuery().getCriteria().andBetween(property, value1, value2);
		}
		return as();
	}

	public T andLike(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andLike(property, value);
		}
		return as();
	}

	public T andLeftLike(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andLeftLike(property, value);
		}
		return as();
	}

	public T andRightLike(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andRightLike(property, value);
		}
		return as();
	}

	public T andLikeColumn(String value, Object column) {
		if (StringUtils.isNotBlank(value) && column != null) {
			newQuery().getCriteria().andLikeColumn(value, column);
		}
		return as();
	}

	public T andNotLike(String property, Object value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andNotLike(property, value);
		}
		return as();
	}

	public T andDateEqualTo(String property, LocalDateTime value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andDateEqualTo(property, value);
		}
		return as();
	}

	public T andDateNotEqualTo(String property, LocalDateTime value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andDateNotEqualTo(property, value);
		}
		return as();
	}

	public T andDateGreaterThan(String property, LocalDateTime value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andDateGreaterThan(property, value);
		}
		return as();
	}

	public T andDateGreaterThanOrEqualTo(String property, LocalDateTime value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andDateGreaterThanOrEqualTo(property, value);
		}
		return as();
	}

	public T andDateLessThan(String property, LocalDateTime value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andDateLessThan(property, value);
		}
		return as();
	}

	public T andDateLessThanOrEqualTo(String property, LocalDateTime value) {
		if (StringUtils.isNotBlank(property) && value != null) {
			newQuery().getCriteria().andDateLessThanOrEqualTo(property, value);
		}
		return as();
	}

	public T andDateIn(String property, List<LocalDateTime> values) {
		if (StringUtils.isNotBlank(property) && values != null) {
			newQuery().getCriteria().andDateIn(property, values);
		}
		return as();
	}

	public T andDateNotIn(String property, List<LocalDateTime> values) {
		if (StringUtils.isNotBlank(property) && values != null) {
			newQuery().getCriteria().andDateNotIn(property, values);
		}
		return as();
	}

	public T andDateBetween(String property, LocalDateTime value1, LocalDateTime value2) {
		if (StringUtils.isNotBlank(property) && value1 != null && value2 != null) {
			newQuery().getCriteria().andDateBetween(property, value1, value2);
		}
		return as();
	}

	public T andDateNotBetween(String property, LocalDateTime value1, LocalDateTime value2) {
		if (StringUtils.isNotBlank(property) && value1 != null && value2 != null) {
			newQuery().getCriteria().andDateNotBetween(property, value1, value2);
		}
		return as();
	}

	public T orderBy(String orderByClause) {
		if (StringUtils.isNotBlank(orderByClause)) {
			newQuery().setOrderByClause(orderByClause);
		}
		return as();
	}

	public T orderBy(OrderBy order) {
		if (order != null) {
			newQuery().setOrderByClause(order);
		}
		return as();
	}

	public T select(String column) {
		if (StringUtils.isNotBlank(column)) {
			newQuery().select(column);
		}
		return as();
	}

	// #################### 数据库操作 #############################

	public T get() {

		/*
		 * 走 Get 主键
		 */
		if (this.id != null) {
			return this.getSqlRunner().get(id);
		}

		/*
		 * 走 GetSelective
		 */
		else if (this._query != null) {
			return this.getSqlRunner().getSelective(_query);
		}

		/*
		 * 走 GetUnique
		 */
		return this.getSqlRunner().getUnique(as());
	}

	public T save() {
		if (this.id == null) {
			return this.insert();
		}
		return this.update();
	}

	public T insert() {
		if (this._batch != null && this._batch.size() > 0) {
			this._affectedRows = this.batchInsert();
		}
		this._affectedRows = this.getSqlRunner().insert(as());
		return as();
	}

	/**
	 * 注意：如果唯一索引中有 DelAt， replace、toggle、 stat 不起作用
	 * 
	 * @return
	 */
	@Deprecated
	public T replace() {
		this._affectedRows = this.getSqlRunner().replace(as());
		return as();
	}

	@Deprecated
	public T toggle() {
		this._affectedRows = this.getSqlRunner().toggle(as());
		return as();
	}

	public int batchInsert() {
		if (this._batch == null) {
			return 0;
		}
		return this.getSqlRunner().batchInsert(this._batch);
	}

	public int delete() {
		this.checkUpdate();
		if (this._batch != null && this._batch.size() > 0) {
			this.batchDelete();
			return this._batch.size();
		} else if (this.id == null) {
			return this.deleteSelective();
		}
		return this.getSqlRunner().delete(as());
	}

	public int deleteSelective() {
		this.checkUpdate();
		return this.getSqlRunner().deleteSelective(as());
	}

	public int batchDelete() {
		if (this._batch == null) {
			return 0;
		}
		return this.getSqlRunner().batchDelete(this._batch);
	}

	public T update() {
		this.checkUpdate();
		if (this._batch != null && this._batch.size() > 0) {
			this._affectedRows = this.batchUpdate();
		} else if (this.id == null) {
			this._affectedRows = this.updateSelective();
		} else {
			this._affectedRows = this.getSqlRunner().update(as());
		}
		return as();
	}

	public int updateSelective() {
		this.checkUpdate();
		return this.getSqlRunner().updateSelective(as());
	}

	public int updative() {
		return this.getSqlRunner().updative(Updative.of(this, this._query));
	}

	public int batchUpdate() {
		if (this._batch == null) {
			return 0;
		}
		return this.getSqlRunner().batchUpdate(this._batch);
	}

	@Deprecated
	public int clearSelective() {
		return this.getSqlRunner().clearSelective(this._query);
	}

	public T lock() {
		return this.getSqlRunner().lock(id);
	}

	public boolean exists() {
		return this.getSqlRunner().exists(id);
	}

	public List<T> query() {
		if (this._query.columns != null && this._limit > 0) {
			return this.getSqlRunner().querySelectiveByCondition(_query, this._limit);
		}
		if (this._query.columns != null) {
			return this.getSqlRunner().querySelectiveByCondition(_query);
		}
		if (this._limit > 0) {
			return this.getSqlRunner().queryByCondition(_query, this._limit);
		}
		return this.getSqlRunner().queryByCondition(_query);
	}

	public Page page() {
		if (_param == null) {
			_param = new Parameters();
		}
		if (this._query.columns != null) {
			return this.getSqlRunner().queryForSelectivePage(_query, _param);
		}
		return this.getSqlRunner().queryForPage(_query, _param);
	}

	public Integer count() {
		return this.getSqlRunner().countByCondition(_query);
	}

	public int updateOrInsertResult() {
		return this._affectedRows;
	}

	public T persist(boolean updateSelective) {
		if (IdGen.isInvalidId(this.getId())) {
			return this.insert();
		}
		if (updateSelective) {
			this.updateSelective();
		} else {
			this.update();
		}
		return as();
	}

	public T persist() {
		return this.persist(true);
	}

	@Deprecated
	public T statistic() {
		this.getSqlRunner().statistic(as());
		return as();
	}

	public T merge() {
		this.getSqlRunner().merge(as());
		return as();
	}

	public T merge(boolean insert) {
		this.getSqlRunner().merge(as(), insert);
		return as();
	}

	protected void checkUpdate() {
		Assert.isTrue(this._query == null || !this._query.isValid(),
				"Please set delete or update condition in object field, do not use query confition!");
	}

	// #################### 基础模型 #############################

	protected BaseMapper<T, PK> getSqlRunner() {
		if (_mapper == null) {
			this._mapper = (BaseMapper<T, PK>) App.getBean(getTableDefine().getMapper());
		}
		return this._mapper;
	}

	protected TableDefine<T> getTableDefine() {
		if (this._table == null) {
			TableDefine<T> _table = (TableDefine<T>) TableDefines.lookup(this.getClass());
			if (_table == null) {
				throw new RuntimeException(String.format("Please define %sMapper that extends BaseMapper.",
						com.swak.utils.StringUtils.upperCaseFirstOne(this.getClass().getSimpleName())));
			}
			this._table = (TableDefine<T>) _table;
		}
		return this._table;
	}
}
